﻿// -------------------------------------------------------------------------------------------------------------------------------
// <copyright file="CrcsSparseMatrix{T}.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>
//     Exul
// </author>
// -------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.Mathematics.LinearAlgebra
{
    #region Usings

    using System;

    using ExulLibrary.Exceptions;

    #endregion

    /// <summary>
    ///     Represents a sparse matrix with the compressed storage with row-major order for the low triangle and
    ///     column-major order for the high triangle.
    /// </summary>
    /// <typeparam name="T">
    ///     The type of values of matrix elements.
    /// </typeparam>
    public sealed class CrcsSparseMatrix<T> : Matrix<T>
    {
        #region Fields

        #region private

        /// <summary>
        ///     The registration index that uniquely identify matrix type.
        /// </summary>
        private static readonly int RegistrationIndex = Global.GetMatrixRegistrationIndex(typeof(CrcsSparseMatrix<object>));

        #endregion

        #endregion

        #region Constructors and Destructors

        #region public

        /// <summary>
        ///     Initializes a new instance of the <see cref="CrcsSparseMatrix{T}"/> class.
        /// </summary>
        /// <param name="lowTriangleRowsBegins">
        ///     The collection of indexes of rows begins in the low triangle.
        /// </param>
        /// <param name="lowTriangleColumnsIndexes">
        ///     The collection of zero-based indexes of columns of nonzero elements in the low triangle.
        /// </param>
        /// <param name="highTriangleColumnsBegins">
        ///     The collection of indexes of columns begins in the high triangle.
        /// </param>
        /// <param name="highTriangleRowsIndexes">
        ///     The collection of zero-based indexes of rows of nonzero elements in the high triangle.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="lowTriangleRowsBegins"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="lowTriangleColumnsIndexes"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleColumnsBegins"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleRowsIndexes"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NoEnoughElementsException">
        ///     <paramref name="lowTriangleRowsBegins"/> has count of elements less than 2.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="lowTriangleColumnsIndexes"/> has count of elements less than
        ///         <paramref name="lowTriangleRowsBegins"/>[rowsCount] - <paramref name="lowTriangleRowsBegins"/>[0].
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleColumnsBegins"/> has count of elements less than 2.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleRowsIndexes"/> has count of elements less than
        ///         <paramref name="highTriangleColumnsBegins"/>[columnsCount] - <paramref name="highTriangleColumnsBegins"/>[0].
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows is determined as length of <paramref name="lowTriangleRowsBegins"/> minus 1.
        ///     The number of columns is determined as length of <paramref name="highTriangleColumnsBegins"/> minus 1.
        ///     The elements of <paramref name="lowTriangleRowsBegins"/> and <paramref name="highTriangleColumnsBegins"/>
        ///     should be nonnegative and
        ///     each element of <paramref name="lowTriangleRowsBegins"/> and <paramref name="highTriangleColumnsBegins"/>
        ///     should be equal to or greater than it's previous element, but both conditions aren't verified in the constructor.
        /// </remarks>
        public CrcsSparseMatrix(
            int[] lowTriangleRowsBegins,
            int[] lowTriangleColumnsIndexes,
            int[] highTriangleColumnsBegins,
            int[] highTriangleRowsIndexes)
            : this(
                lowTriangleRowsBegins,
                lowTriangleColumnsIndexes,
                highTriangleColumnsBegins,
                highTriangleRowsIndexes,
                null,
                null,
                null)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="CrcsSparseMatrix{T}"/> class.
        /// </summary>
        /// <param name="lowTriangleRowsBegins">
        ///     The collection of indexes of rows begins in the low triangle.
        /// </param>
        /// <param name="lowTriangleColumnsIndexes">
        ///     The collection of zero-based indexes of columns of nonzero elements in the low triangle.
        /// </param>
        /// <param name="highTriangleColumnsBegins">
        ///     The collection of indexes of columns begins in the high triangle.
        /// </param>
        /// <param name="highTriangleRowsIndexes">
        ///     The collection of zero-based indexes of rows of nonzero elements in the high triangle.
        /// </param>
        /// <param name="lowTriangleData">
        ///     The collection of values of nonzero elements in the low triangle.
        /// </param>
        /// <param name="diagonalData">
        ///     The collection of values of elements on the diagonal.
        /// </param>
        /// <param name="highTriangleData">
        ///     The collection of values of nonzero elements in the high triangle.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="lowTriangleRowsBegins"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="lowTriangleColumnsIndexes"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleColumnsBegins"/> is <see langword="null"/>.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleRowsIndexes"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NoEnoughElementsException">
        ///     <paramref name="lowTriangleRowsBegins"/> has count of elements less than 2.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="lowTriangleColumnsIndexes"/> has count of elements less than
        ///         <paramref name="lowTriangleRowsBegins"/>[rowsCount] - <paramref name="lowTriangleRowsBegins"/>[0].
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleColumnsBegins"/> has count of elements less than 2.
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleRowsIndexes"/> has count of elements less than
        ///         <paramref name="highTriangleColumnsBegins"/>[columnsCount] - <paramref name="highTriangleColumnsBegins"/>[0].
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="diagonalData"/> is not <see langword="null"/> and has count of elements less than
        ///         min(rowsCount, columnsCount).
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="lowTriangleData"/> is not <see langword="null"/> and has count of elements less than
        ///         <paramref name="lowTriangleRowsBegins"/>[rowsCount] - <paramref name="lowTriangleRowsBegins"/>[0].
        ///     </para>
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="highTriangleData"/> is not <see langword="null"/> and has count of elements less than
        ///         <paramref name="highTriangleColumnsBegins"/>[columnsCount] - <paramref name="highTriangleColumnsBegins"/>[0].
        ///     </para>
        /// </exception>
        /// <remarks>
        ///     The number of rows is determined as length of <paramref name="lowTriangleRowsBegins"/> minus 1.
        ///     The number of columns is determined as length of <paramref name="highTriangleColumnsBegins"/> minus 1.
        ///     The elements of <paramref name="lowTriangleRowsBegins"/> and <paramref name="highTriangleColumnsBegins"/>
        ///     should be nonnegative and
        ///     each element of <paramref name="lowTriangleRowsBegins"/> and <paramref name="highTriangleColumnsBegins"/>
        ///     should be equal to or greater than it's previous element, but both condition don't verified in the constructor.
        /// </remarks>
        public CrcsSparseMatrix(
            int[] lowTriangleRowsBegins,
            int[] lowTriangleColumnsIndexes,
            int[] highTriangleColumnsBegins,
            int[] highTriangleRowsIndexes,
            T[] lowTriangleData,
            T[] diagonalData,
            T[] highTriangleData)
        {
            #if SAFE

            if (lowTriangleRowsBegins == null)
            {
                throw new ArgumentNullException("lowTriangleRowsBegins");
            }

            if (lowTriangleColumnsIndexes == null)
            {
                throw new ArgumentNullException("lowTriangleColumnsIndexes");
            }

            if (highTriangleColumnsBegins == null)
            {
                throw new ArgumentNullException("highTriangleColumnsBegins");
            }

            if (highTriangleRowsIndexes == null)
            {
                throw new ArgumentNullException("highTriangleRowsIndexes");
            }

            if (lowTriangleRowsBegins.Length < 2)
            {
                throw new NoEnoughElementsException("lowTriangleRowsBegins", lowTriangleRowsBegins.Length, 2);
            }

            if (highTriangleColumnsBegins.Length < 2)
            {
                throw new NoEnoughElementsException("highTriangleColumnsBegins", highTriangleColumnsBegins.Length, 2);
            }

            #endif

            this.RowsCount = lowTriangleRowsBegins.Length - 1;
            this.ColumnsCount = highTriangleColumnsBegins.Length - 1;

            var elementsCountInLowTriangle = lowTriangleRowsBegins[this.RowsCount] - lowTriangleRowsBegins[0];
            var elementsCountInHighTriangle = highTriangleColumnsBegins[this.ColumnsCount] - highTriangleColumnsBegins[0];
            var elementsCountOnDiagonal = Math.Min(this.RowsCount, this.ColumnsCount);

            #if SAFE

            if (lowTriangleColumnsIndexes.Length < elementsCountInLowTriangle)
            {
                throw new NoEnoughElementsException(
                    "lowTriangleColumnsIndexes", lowTriangleColumnsIndexes.Length, elementsCountInLowTriangle);
            }

            if (highTriangleRowsIndexes.Length < elementsCountInHighTriangle)
            {
                throw new NoEnoughElementsException(
                    "highTriangleRowsIndexes", highTriangleRowsIndexes.Length, elementsCountInHighTriangle);
            }

            if ((diagonalData != null) && (diagonalData.Length < elementsCountOnDiagonal))
            {
                throw new NoEnoughElementsException("diagonalData", diagonalData.Length, elementsCountOnDiagonal);
            }

            if ((lowTriangleData != null) && (lowTriangleData.Length < elementsCountInLowTriangle))
            {
                throw new NoEnoughElementsException("lowTriangleData", lowTriangleData.Length, elementsCountInLowTriangle);
            }

            if ((highTriangleData != null) && (highTriangleData.Length < elementsCountInHighTriangle))
            {
                throw new NoEnoughElementsException("highTriangleData", highTriangleData.Length, elementsCountInHighTriangle);
            }

            #endif

            this.LowTriangleRowsBegins = lowTriangleRowsBegins;
            this.LowTriangleColumnsIndexes = lowTriangleColumnsIndexes;
            this.HighTriangleColumnsBegins = highTriangleColumnsBegins;
            this.HighTriangleRowsIndexes = highTriangleRowsIndexes;
            this.DiagonalValues = diagonalData ?? new T[elementsCountOnDiagonal];
            this.LowTriangleValues = lowTriangleData ?? new T[elementsCountInLowTriangle];
            this.HighTriangleValues = highTriangleData ?? new T[elementsCountInHighTriangle];
        }

        #endregion

        #endregion

        #region Properties

        #region public

        /// <summary>
        ///     Gets the collection of values of elements on the diagonal.
        /// </summary>
        /// <value>
        ///     The collection of values of elements on the diagonal.
        /// </value>
        public T[] DiagonalValues
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the collection of indexes of columns begins in the high triangle.
        /// </summary>
        /// <value>
        ///     The collection of indexes of columns begins in the high triangle.
        /// </value>
        public int[] HighTriangleColumnsBegins
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets The collection of zero-based indexes of rows of nonzero elements in the high triangle.
        /// </summary>
        /// <value>
        ///     The collection of zero-based indexes of rows of nonzero elements in the high triangle.
        /// </value>
        public int[] HighTriangleRowsIndexes
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the collection of values of nonzero elements in the high triangle.
        /// </summary>
        /// <value>
        ///     The collection of values of nonzero elements in the high triangle.
        /// </value>
        public T[] HighTriangleValues
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the collection of zero-based indexes of columns of nonzero elements in the low triangle.
        /// </summary>
        /// <value>
        ///     The collection of zero-based indexes of columns of nonzero elements in the low triangle.
        /// </value>
        public int[] LowTriangleColumnsIndexes
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the collection of indexes of rows begins in the low triangle.
        /// </summary>
        /// <value>
        ///     The collection of indexes of rows begins in the low triangle.
        /// </value>
        public int[] LowTriangleRowsBegins
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the collection of values of nonzero elements in the low triangle.
        /// </summary>
        /// <value>
        ///     The collection of values of nonzero elements in the low triangle.
        /// </value>
        public T[] LowTriangleValues
        {
            get;
            private set;
        }

        #endregion

        #endregion

        #region Overridden Properties

        #region public

        /// <summary>
        ///     Gets the registration index of the matrix that uniquely identifies type of matrix.
        /// </summary>
        /// <value>
        ///     The registration index of the matrix that uniquely identifies type of matrix.
        /// </value>
        public override int MatrixRegistrationIndex
        {
            get
            {
                return RegistrationIndex;
            }
        }

        #endregion

        #endregion
    }
}